PATH![]() |
![]() ![]() |
As described in About Multitasking on the Mac OS tasks often need to coordinate with the main application or with other tasks to avoid data corruption or synchronization problems. To coordinate tasks, Multiprocessing Services provides three notification mechanisms (semaphores, event groups, and message queues) as well as critical regions.
Of the three notification mechanisms, message queues are the easiest to use, but they are the slowest. Typically a task has two message queues associated with it. It takes messages off an input queue, processes the information accordingly, and, when done, posts a message to an output queue.
IMPORTANT
You should never use only one instance of a notification mechanism to convey both input and output information, because doing so can easily cause confusion. For example, after posting a request, an application will at some point start waiting for results. If it waits on the same mechanism where the request was posted, the request itself may appear to be the result. The application may then clear the request in the mistaken belief that it was a result and no actual work gets done.
Before notifying a task, your application should make sure that everything the task needs is in memory. That is, you should have created any necessary queues and allocated space for any data the task may require. For each task, your application establishes the parameters of the work that it wants the task to perform and then it must signal the task through either a queue or a semaphore to begin performing that work. The specific work that the task is to perform can be completely defined within a message, or possibly within a block of memory reserved for that task. You can also pass in a pointer to the function that the task should call to perform the work. Doing so allows one task to perform many different types of chores.
Listing 3-4 shows a function that divides up a large amount of data among multiple tasks, placing requests on each task's request queue and waiting for the results.
Listing 3-4 Assigning work to tasks
OSErr NotifyTasks( UInt32 realFirstThing, UInt32 realTotalThings ) {
UInt32 i;
OSErr theErr;
UInt32 thingsPerTask;
UInt32 message;
sWorkParams appData;
theErr = noErr;
thingsPerTask = realTotalThings / numProcessors;
/* Start each task working on a unique piece of the total data */
for( i = 0; i < numProcessors; i++ ) {
myTaskData[i].params.firstThing =
realFirstThing + thingsPerTask * i;
myTaskData[i].params.totalThings = thingsPerTask;
message = kMyRequestOne;
MPNotifyQueue( myTaskData[i].requestQueue, (void *)message,
NULL, NULL );
}
/* Now wait for the tasks to finish */
for( i = 0; i < numProcessors; i++ )
MPWaitOnQueue( myTaskData[i].resultQueue, (void **)&message,
NULL, NULL, kDurationForever );
return( theErr );
}
For each task, it calls MPNotifyQueue to place the pointer to the task's portion of the data on the task's request queue. It then calls MPWaitOnQueue to wait for confirmation that the task has completed.
Note
A message queue message is passed to the queue as three 32-bit parameters. Because the message in Listing 3-4 is only 32-bits long, the remaining two parameters are set to NULL .
If you want to use semaphores or event groups instead of message queues, you would call the following functions to set up, notify, and wait on them, in a manner similar to that shown in Listing 3-4 :
However, if you use the simpler notification mechanisms, you have to find another way to pass the function pointer to the task. One possibility is to assign the pointer to a field in the task's task data structure.
Note that the example in Listing 3-4 will wait forever ( kDurationForever ) for a message to appear on its result queue. While this method is fine if called from a preemptive task, it can cause problems if called from a cooperative task. If the task takes a significant amount of time to execute, the calling task "hangs" for that time, since it can't call WaitNextEvent to give other applications processor time. If you want to wait on a task from a cooperative task, your application should post the message and then return to its event loop. From within the event loop it can then poll the result queue using kDurationImmediate waits until a message appears.
If you specify kDurationImmediate for the waiting time for either MPWaitOnQueue , MPWaitOnSemaphore , MPWaitForEvent , or MPEnterCriticalRegion , the function always returns immediately. If the return value is kMPTimeoutErr , then the task generated no new results since the last time the application checked. That is, no message was available, the semaphore was zero, or the critical region was being executed by another processor. If the value is noErr , a result was present and obtained by the call.